/*
 * CVisualisator.cpp
 *
 *  Created on: 04.09.2011
 *      Author: Blinov Kirill
 *      Copyright: LGPL
 */

#include "CVisualisator.h"

const UINT SHADERS_ARRAY_FRAGMENTATION_THREASHOLD = 10;

#define ELEMENTS_IN_UNIFORM_STRUCTURE(structure,elmtype) sizeof(structure) / sizeof(elmtype)
#define VAR_TO_STR(variable) #variable
#define GLF_BUFFER_OFFSET(i) ((char *)NULL + (i))

CVisualisator::CVisualisator()
{
	cameraPreprocessor = new CCameraPreprocessor();
	renderer = new CRenderer();
}

CVisualisator::~CVisualisator()
{
	delete cameraPreprocessor;
	delete renderer;
}

bool
CVisualisator::StartVisualisation( TThreadSafeQueue<FULL_FRAME_DATA*>* FrameQueue, TThreadSafeQueue<INPUT_MESSAGE*> *InputMessageQueue )
{
	bool Result = false;
	if ( initialized )
	{
		Result = cameraPreprocessor->Start( FrameQueue, InputMessageQueue, renderer );
	}
	return Result;
}

void
CVisualisator::StopVisualisation()
{
	if ( initialized )
	{
		cameraPreprocessor->Stop();
	}
}

/*
 * The function does nothing now. For feature tunes only.
 */
void
CVisualisator::ConfigVisualisation( )
{
	if ( initialized )
	{
		VISUAL_PARAMS Parametrs;
		renderer->SetRenderParams( Parametrs  );
	}
}

void
CVisualisator::SetDimension( const SCREEN_DIMENSION &ScreenDimension )
{
	initialized = renderer->Init( ScreenDimension );
}

/*
 * Register models in OpenGL and create array of RENDER_MODEL_INFO and returns render array name
 *
 * @params
 *
 * output:
 * 		GLuint - Render array name
 *
 */
GLuint
CVisualisator::SetUpModels( const std::vector<PURE_COMPRESSED_MODEL> &ProcessedModels )
{
    GLuint RenderArrayName = 0;

	if ( initialized )
	{
        UINT TotalVertexCount = 0; //Vertex count in all models

        //Calculate common vertex buffer size
		for  ( std::vector<PURE_COMPRESSED_MODEL>::const_iterator It = ProcessedModels.begin(),
						   EndIterator = ProcessedModels.end();
						   It != EndIterator; ++It )
		{
			const PURE_COMPRESSED_MODEL* Model = It.base();
			TotalVertexCount += Model->vertices.size();
		}

        STANDART_VERTEX *CommonVertexArray = new STANDART_VERTEX[TotalVertexCount];
        UINT             VertexArrayCursor = 0;

        //Copy vertices to the common array
		for  ( std::vector<PURE_COMPRESSED_MODEL>::const_iterator It = ProcessedModels.begin(),
						   EndIterator = ProcessedModels.end();
						   It != EndIterator; ++It )
		{
			const PURE_COMPRESSED_MODEL* Model = It.base();
			memcpy( (void*)( &CommonVertexArray[VertexArrayCursor] ), Model->vertices.data(),
					sizeof(STANDART_VERTEX) * Model->vertices.size() );

			Model->renderInfo->offsetInVertexBuffer = VertexArrayCursor; //Save offset
			VertexArrayCursor += Model->vertices.size();
		}


		//for ( int i = 0; i < TotalVertexCount; i++ )
		//{
		//	glm::vec3 position;
		//	glm::vec3 normal;
		//	glm::vec2 texture;
		//	fprintf( stderr, "%i position: %f, %f, %f; normal: %f, %f, %f; texture: %f, %f\n",
		//			 i, CommonVertexArray[i].position.x, CommonVertexArray[i].position.y, CommonVertexArray[i].position.z,
		//			 CommonVertexArray[i].normal.x, CommonVertexArray[i].normal.y, CommonVertexArray[i].normal.z,
		//			 CommonVertexArray[i].texture.x,  CommonVertexArray[i].texture.y );
		//}

		GLuint tmpVertexBufferName;
	    glGenBuffers(1, &tmpVertexBufferName);
	    glBindBuffer(GL_ARRAY_BUFFER, tmpVertexBufferName);
	    glBufferData(GL_ARRAY_BUFFER, TotalVertexCount * sizeof( STANDART_VERTEX ), CommonVertexArray, GL_STATIC_DRAW);
	    glBindBuffer(GL_ARRAY_BUFFER, 0);

	    glGenVertexArrays( 1, &RenderArrayName );
	    glBindVertexArray( RenderArrayName );

		glBindBuffer( GL_ARRAY_BUFFER, tmpVertexBufferName );

		glVertexAttribPointer( 0, ELEMENTS_IN_UNIFORM_STRUCTURE( CommonVertexArray->position, float ), GL_FLOAT, GL_FALSE,
				               sizeof( STANDART_VERTEX ), GLF_BUFFER_OFFSET(0) );
		glVertexAttribPointer( 1, ELEMENTS_IN_UNIFORM_STRUCTURE( CommonVertexArray->normal, float ), GL_FLOAT, GL_FALSE,
				               sizeof( STANDART_VERTEX ), GLF_BUFFER_OFFSET( sizeof(CommonVertexArray->position) ) );
		glVertexAttribPointer( 2, ELEMENTS_IN_UNIFORM_STRUCTURE( CommonVertexArray->texture, float ), GL_FLOAT, GL_FALSE,
				               sizeof( STANDART_VERTEX ), GLF_BUFFER_OFFSET( sizeof(CommonVertexArray->position) +
				            		                                         sizeof(CommonVertexArray->normal) ) );
		glVertexAttribPointer( 3, ELEMENTS_IN_UNIFORM_STRUCTURE( CommonVertexArray->bonesWeights, float ), GL_FLOAT, GL_FALSE,
				               sizeof( STANDART_VERTEX ), GLF_BUFFER_OFFSET( sizeof(CommonVertexArray->position) +
				            		                                         sizeof(CommonVertexArray->normal) +
				            		                                         sizeof(CommonVertexArray->texture) ) );
		glVertexAttribPointer( 4, ELEMENTS_IN_UNIFORM_STRUCTURE( CommonVertexArray->bonesIndexesAndTextureNumber, float ),
				               GL_FLOAT, GL_FALSE, sizeof( STANDART_VERTEX ),
				                                          GLF_BUFFER_OFFSET( sizeof(CommonVertexArray->position) +
				            		                                         sizeof(CommonVertexArray->normal) +
				            		                                         sizeof(CommonVertexArray->texture) +
				            		                                         sizeof(CommonVertexArray->bonesWeights) ) );

		glBindBuffer(GL_ARRAY_BUFFER, 0);

		glEnableVertexAttribArray(0);
		glEnableVertexAttribArray(1);
		glEnableVertexAttribArray(2);
		glEnableVertexAttribArray(3);
		glEnableVertexAttribArray(4);

	    glBindVertexArray(0);

	    delete[] CommonVertexArray; //Free vertices array, it's already copied
	}
	return RenderArrayName;
}

GLuint
CVisualisator::SetUpShader( const std::string FileName )
{
	GLuint Result = 0;

	if ( initialized )
	{
#ifdef ENGINE_DEBUG
		std::string *ShaderSource = NULL;
#endif

		GLenum ShaderType;
		size_t Position = FileName.rfind( '.' );  //Get . position in file name
		std::string Extension = FileName.substr ( Position );     //Get extension of the file
		if ( ".vert" == Extension )
		{
			ShaderType = GL_VERTEX_SHADER;
		}
		else if ( ".frag" == Extension )
		{
			ShaderType = GL_FRAGMENT_SHADER;
		}

		fprintf(stdout, "ShaderType: 0x%X\n", ShaderType);
		if ( ShaderType != 0 )
		{
#ifdef ENGINE_DEBUG
			Result = LoadShader( FileName, ShaderType, &ShaderSource );
			bool CheckResult = CheckShader( Result, ShaderSource->c_str() );
			fprintf(stdout, "Shader load %s \n Shader code: %s\n", CheckResult ? "success" : "fail",
			        ShaderSource->c_str() );
			if ( ShaderSource != NULL )
			{
				delete ShaderSource;
			}
#else
			Result = LoadShader( FileName, ShaderType, NULL );
#endif
		}
	}
	return Result;
}

/*
 * This function creates vertex program from vertex and fragment shaders
 * Parameters:
 *   in  VertexShaderName - vertex shader name
 *   in  PixelShaderName  - fragment shader name
 *   out ShaderName       - result program name
 *
 * Returns result of compilation.
 */
bool
CVisualisator::LinkShaderProgramm( const GLuint VertexShaderName, const GLuint PixelShaderName, IShaderProgram **Shader )
{
	GLuint ProgramName = glCreateProgram();

	glAttachShader(ProgramName, VertexShaderName);
	glAttachShader(ProgramName, PixelShaderName);

	glDeleteShader(VertexShaderName);
	glDeleteShader(PixelShaderName);

	glLinkProgram( ProgramName );

	bool Result = CheckProgram( ProgramName );
	if ( Result )
	{
		*Shader = new CShaderProgram( ProgramName );
	}
	return Result;
}

void
CVisualisator::DeleteTexture( const GLuint Name )
{
	DeleteTexture( std::vector<GLuint>( 1, Name ) );
}

void
CVisualisator::DeleteTexture( const std::vector<GLuint> Names )
{
	glDeleteTextures( Names.size(), Names.data() );
}

void
CVisualisator::DeleteVertexBuffer( const GLuint Name )
{
	DeleteVertexBuffer( std::vector<GLuint>( 1, Name ) );
}

void
CVisualisator::DeleteVertexBuffer( const std::vector<GLuint> Name )
{
	glDeleteVertexArrays( Name.size(), Name.data() );
}


GLuint
CVisualisator::LoadShader( const std::string FileName, const GLenum ShaderType, std::string** ShaderSource )
{
	GLuint Result = 0;
	if ( !FileName.empty() )
	{
		std::string *tmpSourceCode = LoadFile( FileName );

		Result = glCreateShader( ShaderType );

		char const * SourcePointer = tmpSourceCode->c_str();
		glShaderSource( Result, 1, &SourcePointer, NULL );
		glCompileShader( Result );

		if ( ShaderSource != NULL )
		{
			*ShaderSource = tmpSourceCode;
		}
		else
		{
			delete tmpSourceCode;
		}
	}
	return Result;
}

bool
CVisualisator::CheckShader( const GLuint ShaderName, const char* Source)
{
	GLint Result = GL_FALSE;
	if( ShaderName > 0 )
	{
		glGetShaderiv(ShaderName, GL_COMPILE_STATUS, &Result);
		fprintf(stdout, "Compiling shader\n%s...\n", Source);

		int InfoLogLength;
		glGetShaderiv(ShaderName, GL_INFO_LOG_LENGTH, &InfoLogLength);

		std::vector<char> Buffer(InfoLogLength);
		glGetShaderInfoLog(ShaderName, InfoLogLength, NULL, &Buffer[0]);
	}
	return ( Result == GL_TRUE );
}

bool
CVisualisator::CheckProgram(GLuint ProgramName)
{
	GLint Result = GL_FALSE;

	if( ProgramName > 0 )
	{
		glGetProgramiv(ProgramName, GL_LINK_STATUS, &Result);
		fprintf(stdout, "Checking program\n");

		int InfoLogLength;
		glGetProgramiv(ProgramName, GL_INFO_LOG_LENGTH, &InfoLogLength);
		std::vector<GLchar> Buffer(std::max(InfoLogLength, int(1)));
		glGetProgramInfoLog(ProgramName, InfoLogLength, NULL, &Buffer[0]);
		fprintf(stdout, "%s\n", &Buffer[0]);
	}

	return ( GL_TRUE == Result );
}

std::string*
CVisualisator::LoadFile( const std::string& Filename )
{
	std::string *Text = new std::string("");
	std::ifstream stream( Filename.c_str(), std::ios::in );
	if( stream.is_open() )
	{
		std::string Line = "";

		while( getline(stream, Line) )
		{
			(*Text) += "\n" + Line;
		}
		stream.close();
	}
	return Text;
}
